package com.config;

import com.advice.RequestBodyDecryptAdvice;
import com.conversionservice.DateTimeConversionService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.methodargumentresolver.UserMethodArgumentResolver;
import com.methodreturnvaluehandler.ByteArrayReturnValueHandler;
import org.springframework.beans.*;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServer;
import org.springframework.boot.web.servlet.ServletContextInitializerBeans;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;


import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.boot.web.servlet.server.Session;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.*;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.GenericServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.beans.PropertyEditor;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static org.springframework.boot.web.servlet.server.Session.SessionTrackingMode.COOKIE;

/**
 * 容器创建Servlet容器，Servlet容器初始化DispatcherServlet
 */
@Configuration
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebServerConfig {


    /**
     * 0、{@link ServletWebServerApplicationContext} 依赖于此对象来创建 WebServer
     * 内嵌 webServlet 容器工厂
     * 最终在 this.webServer = factory.getWebServer(getSelfInitializer());
     * 创建 WebServer 的途中，会调用 servlet (这里具体是DispatcherServlet)的 init()
     * <p><p>
     * 最终在 selfInitialize() 中初始化 DispatcherServlet
     *
     * @return 用于创建 WebServer 的工厂对象
     * @see ServletWebServerApplicationContext#selfInitialize(javax.servlet.ServletContext)
     * @see WebServer
     * @see ServletWebServerApplicationContext#getWebServerFactory()
     */
    @Bean
    public ServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
        TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
        serverFactory.setPort(serverProperties.getPort());

        Session session = new Session();

        session.setTrackingModes(
                // 如果不配置的话
                // 调用 jakarta.servlet.http.HttpServletResponse.encodeRedirectURL
//              如果请求含有 sessionId ,则会把 sessionId 拼接在 url 后面
//                例如 http://localhost/a/c;jsessionId=asdff?a=1&n=2
//                如果是浏览器重定向访问上面的地址,则会报 400 bad request 异常
                Collections.singleton(COOKIE)
        );

        serverFactory.setSession(session);

        return serverFactory;
    }


    /**
     * 这里的所有步骤由Servlet容器自身发起调用
     * <p>
     * 1、Servlet 容器最终调用 getServlet()
     * <p>
     * {@link ServletRegistrationBean#getServlet()}
     * {@link ServletContextInitializerBeans#addServletContextInitializerBean(String, org.springframework.boot.web.servlet.ServletContextInitializer, org.springframework.beans.factory.ListableBeanFactory)}
     * <p>
     * <p>
     * 2、把 DispatcherServlet 配置为整个 Servlet 容器的入口
     * {@link ServletRegistrationBean#addRegistration(String, javax.servlet.ServletContext)}
     * <p>
     * <p>
     * 3、注册一个容器生命周期钩子对象
     * {@link org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle}
     * <p>
     * <p>
     * 4、由此对象执行 webServer的启动
     * {@link org.springframework.boot.web.servlet.context.WebServerStartStopLifecycle#start()}
     * <p>
     * <p>
     */
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
//        配置servlet容器在启动时对 dispatcherServlet 进行初始化
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        return registrationBean;
    }

    /**
     * 5、Servlet容器通过 Servlet.init() 接口来初始化 DispatcherServlet 对象
     * <p>
     * <p>
     * <p>
     * {@link Servlet#init(javax.servlet.ServletConfig)}
     * <p>
     * <p>
     * {@link GenericServlet#init(javax.servlet.ServletConfig)}
     * <p>
     * <p>
     * {@link FrameworkServlet#initWebApplicationContext()}
     * <p>
     * <p>
     * {@link DispatcherServlet#onRefresh(org.springframework.context.ApplicationContext)}
     */
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    /**
     * 6、ApplicationContext 通过 afterPropertiesSet 方法对其进行初始化
     * <p>
     * <p>
     * {@link  RequestMappingHandlerMapping#afterPropertiesSet()}
     * <p>
     * <p>
     * <p>
     * 7、遍历每一个 bean
     * {@link  AbstractHandlerMethodMapping#processCandidateBean(String)}
     * <p>
     * <p>
     * <p>
     * 8、判断这个bean是否为 Controller 的请求处理方法（Handler）
     * {@link  RequestMappingHandlerMapping#isHandler(Class)}
     * <p>
     * <p>
     * <p>
     * 9、解析 @RequestMapping 及其相关注解，并创建 RequestMappingInfo
     * {@link  RequestMappingHandlerMapping#createRequestMappingInfo(java.lang.reflect.AnnotatedElement)}
     * {@link  RequestMappingInfo}
     * <p>
     * <p>
     * <p>
     * 10、注册此 handler
     * {@link  RequestMappingHandlerMapping#registerHandlerMethod(Object, java.lang.reflect.Method, org.springframework.web.servlet.mvc.method.RequestMappingInfo)}
     * <p>
     * <p>
     * <p>
     * 二、前端请求抵达时
     * <p>
     * <p>
     * <p>
     * 1、Servlet 容器（这里是Tomcat）调用 Servlet Api 中的 service()  方法
     * {@link  HttpServlet#service(ServletRequest, ServletResponse)}
     * <p>
     * <p>
     * <p>
     * 2、DispatcherServlet 调用 doDispatch() 来做后续处理
     * {@link  DispatcherServlet#doDispatch(HttpServletRequest, HttpServletResponse)}
     * <p>
     * <p>
     * <p>
     * 3、DispatcherServlet 委派 HandlerMapping 来创建 HandlerExecutionChain
     * {@link  HandlerExecutionChain}
     * {@link  AbstractHandlerMapping#getHandler(HttpServletRequest)}
     * <p>
     * <p>
     * 4、找到支持此 handler 的 handler适配器
     * {@link  DispatcherServlet#getHandlerAdapter(Object)}
     * <p>
     * {@link  HandlerAdapter}
     * spring mvc 的核心SPI，目的是将 servlet容器 的请求响应适配到 自定义的 Handler 方法上。
     *
     * <p>
     * <p>
     * <p>
     * 5、使用 {@link RequestMappingHandlerAdapter} 调用 {@link ServletInvocableHandlerMethod} 来处理请求与响应
     *
     * <pre>{@code // Actually invoke the handler.
     * 				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
     * }</pre>
     * <p>
     * {@link  RequestMappingHandlerAdapter#invokeHandlerMethod(HttpServletRequest, HttpServletResponse, HandlerMethod)}
     *
     * <h2>准备用于数据绑定的 {@link WebDataBinder}</h2>
     * <p>
     * A、根据 {@link HandlerMethod} 创建 {@link WebDataBinderFactory}
     * <p>
     * {@link RequestMappingHandlerAdapter#getDataBinderFactory(HandlerMethod)}
     * <p>
     * B、从 {@link HandlerMethod } 提取出标记了 {@link InitBinder} 注解的方法，用于初始化{@link WebDataBinderFactory}，
     * 由于每次都是单独创建 {@link WebDataBinderFactory}，所以不同 Controller 之间的 {@link InitBinder} 并不会相互通用
     * <p>
     * C、根据 {@link HandlerMethod} 创建 {@link ServletInvocableHandlerMethod}
     * <p>
     * {@link RequestMappingHandlerAdapter#createInvocableHandlerMethod(HandlerMethod)}
     * <p>
     * D、为{@link ServletInvocableHandlerMethod} 配置
     * <ul>
     *     <li>{@link HandlerMethodArgumentResolverComposite} 方法参数解析器</li>
     *     <li>{@link HandlerMethodReturnValueHandlerComposite} 方法返回值解析器</li>
     *     <li>{@link WebDataBinderFactory} 数据绑定工厂</li>
     *     <li>{@link ParameterNameDiscoverer} handler 方法参数名解析器</li>
     * </ul>
     * <p>
     * 调用
     * {@link  ServletInvocableHandlerMethod#invokeAndHandle(ServletWebRequest, ModelAndViewContainer, Object...)}
     * <p>
     * <p>
     * 6、解析参数并用来调用目标方法
     * <p>
     * {@link  InvocableHandlerMethod#invokeForRequest(NativeWebRequest, ModelAndViewContainer, Object...)}
     * <p>
     * <p>
     * （1）{@link ServletInvocableHandlerMethod} 委派 HandlerMethodArgumentResolverComposite 来绑定参数
     * {@link  InvocableHandlerMethod#getMethodArgumentValues(NativeWebRequest, ModelAndViewContainer, Object...)}
     * <p>
     * <p>
     * （2）{@link HandlerMethodArgumentResolverComposite} 委派 多种 {@link HandlerMethodArgumentResolver} 实现来绑定参数
     * {@link  HandlerMethodArgumentResolverComposite#resolveArgument(MethodParameter, ModelAndViewContainer, NativeWebRequest, WebDataBinderFactory)}
     * <p>
     * <p>
     * （3）找到能处理此请求的 {@link HandlerMethodArgumentResolver}
     * 这里加了查询缓存
     * {@link  HandlerMethodArgumentResolverComposite#getArgumentResolver(MethodParameter)}
     * <p>
     * <p>
     * （4）调用上一步找到的{@link HandlerMethodArgumentResolver}来解析请求参数
     * {@link  HandlerMethodArgumentResolver#resolveArgument(MethodParameter, ModelAndViewContainer, NativeWebRequest, WebDataBinderFactory)}
     * </p>
     * <p>
     * <p>
     * <h3> 以解析 {@link RequestParam} 注解为例</h3>
     * <p>
     * {@link  RequestParamMethodArgumentResolver#resolveArgument(MethodParameter, ModelAndViewContainer, NativeWebRequest, WebDataBinderFactory)}
     * <ul>
     * <li>
     * a、从 @RequestParam 注解中解析 controller 所需的参数名
     * {@link  RequestParamMethodArgumentResolver#createNamedValueInfo(MethodParameter)}
     * </li>
     * <li>
     * b、如果上一步没有拿到参数名称，则从 {@link MethodParameter#getParameterName()} 中通过反射或编译信息得到参数名称
     * <p>
     * <p>
     * {@link  AbstractNamedValueMethodArgumentResolver#updateNamedValueInfo(MethodParameter, AbstractNamedValueMethodArgumentResolver.NamedValueInfo)}
     * <p>
     * <p>
     * {@link  ParameterNameDiscoverer} 通过反射(javac -parameters)或者本地变量表(javac -g) 的方式获取参数的名称
     * </li>
     * <li>
     * c、根据controller所要的参数名称（上两步得到的参数名），从请求中解析出参数值
     * <p>
     * <p>
     * {@link  RequestParamMethodArgumentResolver#resolveName(String, MethodParameter, NativeWebRequest)}
     * </li>
     * <li>
     * d、创建 {@link WebDataBinder}并使用{@link WebBindingInitializer}将其初始化
     * <p>
     * <p>
     * {@link  DefaultDataBinderFactory#createBinder(NativeWebRequest, Object, String)}
     * <p>
     * <p>
     * 这里自定义的 {@link WebBindingInitializer} 用于设置{@link WebDataBinder}的 {@link ConversionService}
     * <p>
     * {@link  WebServerConfig#conversionServiceWebBindingInitializer()}
     * <p>
     * 也可以应用之前在 {@link ServletRequestDataBinderFactory} 中配置的 标记了 {@link InitBinder} 注解的方法来初始化{@link WebDataBinder}
     * </li>
     * <li>
     * e、把获取到的参数值 转换为 controller 能接收的 对象类型
     * 创建一个{@link SimpleTypeConverter} 的实例对象，
     * 并为其初始化 {@link ConversionService}
     * {@link  DataBinder#getSimpleTypeConverter()}
     * </li>
     *     <li>
     * f、在创建{@link SimpleTypeConverter#SimpleTypeConverter()}的内部，
     * 又委派了{@link org.springframework.beans.TypeConverterDelegate}来履行具体的转换逻辑
     * {@link org.springframework.beans.TypeConverterDelegate} 持有对 {@link SimpleTypeConverter} 对象的引用
     * </li>
     * <li>
     * g、使用上一步创建的{@link TypeConverter} 实例对象（具体是{@link SimpleTypeConverter}），
     * 委派其执行 {@link TypeConverter} 的职责
     * {@link  TypeConverterSupport#convertIfNecessary(Object, Class, MethodParameter)}
     * </li>
     * <li>
     * h、{@link SimpleTypeConverter} 再次委派{@link org.springframework.beans.TypeConverterDelegate}执行转换
     * {@link  TypeConverterSupport#convertIfNecessary(Object, Class, TypeDescriptor)}
     * </li>
     * <li>
     * i、{@link org.springframework.beans.TypeConverterDelegate} 最终执行真正的转换
     * <p>
     * <p>
     * {@link  org.springframework.beans.TypeConverterDelegate#convertIfNecessary(String, Object, Object, Class, TypeDescriptor)}
     * 查看之后发现，没有 {@link PropertyEditor}的时候优先调用 {@link ConversionService}进行转换
     * <p>
     * <p>
     * {@link org.springframework.beans.TypeConverterDelegate} 从 {@link SimpleTypeConverter}中获取 {@link ConversionService}
     * <p>
     * <p>
     * {@link  PropertyEditorRegistrySupport#getConversionService()}
     * </li>
     * <li>
     * j、{@link org.springframework.beans.TypeConverterDelegate} 调用{@link DateTimeConversionService#convert(Object, TypeDescriptor, TypeDescriptor)}
     * 执行转换
     * </li>
     * </ul>
     * <p>
     *
     * <p>
     * <h3>以解析 @RequestBody 注解为例</h3>
     * 所使用的解析器
     * <p>
     * {@link  RequestResponseBodyMethodProcessor#resolveArgument(MethodParameter, ModelAndViewContainer, NativeWebRequest, WebDataBinderFactory)}
     * <p>
     * <p>
     * <p>
     * {@link  AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, MethodParameter, java.lang.reflect.Type)}
     * <p>
     * <p>
     * <p>
     * RequestResponseBodyMethodProcessor 委派调用每一个 {@link HttpMessageConverter} 来解析参数（请求体）
     * <p>
     * 这里使用了通用的{@link GenericHttpMessageConverter}实现来解析json为实体对象，{@link GenericHttpMessageConverter} 是 {@link HttpMessageConverter}的子接口
     * <ul>
     * <li>{@link MappingJackson2HttpMessageConverter} 是 {@link GenericHttpMessageConverter} 的一个实现
     * </li>
     * <li>
     * {@link MappingJackson2HttpMessageConverter} 委派{@link ObjectMapper} 来读取请求体
     * </li>
     * <li>
     * {@link  AbstractJackson2HttpMessageConverter#readJavaType(com.fasterxml.jackson.databind.JavaType, org.springframework.http.HttpInputMessage)}
     * </li>
     * <li>
     * {@link  HttpMessageConverter} 用于将请求转换为特定的 方法参数类型，例如 String byte[]
     * </li>
     * <li>
     * {@link  GenericHttpMessageConverter} 更通用的请求体解析器，可以转换为任意的 方法参数对象
     * </li>
     * <li>
     * {@link  MappingJackson2HttpMessageConverter} 具体实现，用于将请求体中的json对象转换为 方法的实体对象
     * </li>
     * </ul>
     * <p>
     * <p>
     * （5）调用目标方法
     * {@link  InvocableHandlerMethod#doInvoke(Object...)}
     * <p>
     * <p>
     * 7、委派{@link HandlerMethodReturnValueHandlerComposite}解析调用返回的结果
     * <p>
     * <p>
     * <p>
     * （1）委派
     * {@link  HandlerMethodReturnValueHandlerComposite#handleReturnValue(Object, MethodParameter, ModelAndViewContainer, NativeWebRequest)}
     * <p>
     * <p>
     * （2）二次委派
     * {@link  HandlerMethodReturnValueHandlerComposite#selectHandler(Object, MethodParameter)}
     * <p>
     * 解析 @ResponseBody 注解
     * 判断是否支持
     * {@link  RequestResponseBodyMethodProcessor#supportsReturnType(MethodParameter)}
     * <p>
     * <p>
     * （3）调用找到的{@link HandlerMethodReturnValueHandler} 来处理结果
     * <p>
     * 对于 @ResponseBody 注解
     * {@link  RequestResponseBodyMethodProcessor#handleReturnValue(Object, MethodParameter, ModelAndViewContainer, NativeWebRequest)}
     * 委派每一个 {@link HttpMessageConverter} 来获取可以产生的媒体类型（application/json）
     * {@link  AbstractMessageConverterMethodProcessor#getProducibleMediaTypes(HttpServletRequest, Class, java.lang.reflect.Type)}
     * 因为配置了{@link MappingJackson2HttpMessageConverter} 其作为{@link GenericHttpMessageConverter} 接口的一个子类
     * 所以会将结果对象写为JSON
     * <p>
     */
    @Bean
    public HandlerMapping requestMappingHandlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    /**
     * 因为{@link RequestMappingHandlerAdapter} 实现了 {@link InitializingBean} 接口
     * 所以后续初始化时会调用
     *
     * @see RequestMappingHandlerAdapter#afterPropertiesSet()
     * @see RequestMappingHandlerAdapter#initControllerAdviceCache()
     * 从 容器中取出 控制器 通知、请求通知、响应通知 并进行配置
     * @see RequestBodyAdvice
     * @see ResponseBodyAdvice
     * @see ControllerAdviceBean
     */
    @Bean
    public HandlerAdapter handlerAdapter() {

        RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();


//        创建一个通用的 json 消息转换器，否则无法正确解析 @RequestBody 中的对象
        GenericHttpMessageConverter<Object> jsonConverter = new MappingJackson2HttpMessageConverter();

        HttpMessageConverter<String> stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);

        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();


        messageConverters.add(stringHttpMessageConverter);
        messageConverters.add(jsonConverter);

        handlerAdapter.setMessageConverters(messageConverters);

        handlerAdapter.setCustomArgumentResolvers(Collections.singletonList(myHandlerMethodArgumentResolver()));

        handlerAdapter.setCustomReturnValueHandlers(Collections.singletonList(
                byteArrayReturnValueHandler()
        ));

        /**
         * 配置用于 配置 {@link WebDataBinder} 的初始化器，为其添加自定义的 {@link ConversionService}
         * @see RequestMappingHandlerAdapter#getDataBinderFactory(HandlerMethod)
         * @see RequestMappingHandlerAdapter#createDataBinderFactory(java.util.List)
         * 每次创建{@link ServletRequestDataBinderFactory#ServletRequestDataBinderFactory(List, WebBindingInitializer)}的时候，都为其配置 {@link WebBindingInitializer}

         * 这样 每次通过 {@link DefaultDataBinderFactory#createBinder(NativeWebRequest, Object, String)}
         * 创建 {@link WebDataBinder}的时候，就能自动调用这里配置的 {@link WebBindingInitializer} 将其初始化
         */
        handlerAdapter.setWebBindingInitializer(conversionServiceWebBindingInitializer());


        //handlerAdapter 不会自动从 beanFactory 中扫描并添加
        handlerAdapter.setRequestBodyAdvice(
                Collections.singletonList(new RequestBodyDecryptAdvice())
        );

        return handlerAdapter;
    }
/**
 * {@link HandlerAdapter} 只会 自动扫描 {@link ControllerAdvice}
 * 不能自动添加 {@link RequestBodyAdvice}
 * 所以这里放到IOC里面是没用的
    @Bean
    public RequestBodyAdvice requestBodyDecryptAdvice(){
        return new RequestBodyDecryptAdvice();
    }
*/
    @Bean
    public WebBindingInitializer conversionServiceWebBindingInitializer() {
        DateTimeConversionService dateTimeConversionService = new DateTimeConversionService();
        return binder -> binder.setConversionService(dateTimeConversionService);
    }

    @Bean
    public HandlerMethodArgumentResolver myHandlerMethodArgumentResolver() {
        return new UserMethodArgumentResolver();
    }


    @Bean
    public ByteArrayReturnValueHandler byteArrayReturnValueHandler() {
        return new ByteArrayReturnValueHandler();
    }
}
